﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

namespace Platform.Storage.Redis.Hash
{
	public class AnyObject<T> : RealProxy
		where T : IHashObject
	{
		private RedisMessageChannel channel;
		private Dictionary<string, string> values = new Dictionary<string, string>();
		private List<string> changedValues = new List<string>();
		private SendCommandBehavior behavior;

		public AnyObject( string name, RedisMessageChannel channel )
			: this( SendCommandBehavior.Batch, name, channel )
		{ }

		public AnyObject( SendCommandBehavior behavior, string name, RedisMessageChannel channel )
			: base( typeof( T ) )
		{
			this.HashName = name;
			this.channel = channel;
			this.behavior = behavior;
		}

		public override void GetObjectData( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context )
		{
			base.GetObjectData( info, context );
		}

		public override object GetTransparentProxy()
		{
			return base.GetTransparentProxy();
		}

		/// <summary>
		/// Define como será feita a envio e leitura dos dados
		/// </summary>
		public SendCommandBehavior Behavior
		{ get { return this.behavior; } }

		/// <summary>
		/// Obtém ou define a chave para acessar os dados desse objeto no REDIS
		/// </summary>
		public string HashName
		{ get; private set; }

		/// <summary>
		/// Indica se os valores foram lidos do REDIS
		/// </summary>
		public bool ValuesLoaded
		{ get; private set; }

		private void SetAsChanged( string name )
		{
			if ( !this.changedValues.Contains( name ) )
				this.changedValues.Add( name );
		}

		private void SetField( string name, string value )
		{
			switch ( this.behavior )
			{
				case SendCommandBehavior.Batch:
					this.ReadValues( false );
					this.values[ name ] = value;
					this.SetAsChanged( name );
					break;
				case SendCommandBehavior.SendEach:
				default:
					this.SetValue( name, value );
					break;
			}
		}

		private string GetField( string name )
		{
			switch ( this.behavior )
			{
				case SendCommandBehavior.Batch:
					this.ReadValues( false );
					string value = null;

					if ( this.values.TryGetValue( name, out value ) )
						return value;
					else
						return null;
				case SendCommandBehavior.SendEach:
				default:
					return this.GetValue( name );
			}
		}

		public override IMessage Invoke( IMessage msg )
		{
			IMethodCallMessage callMessage = msg as IMethodCallMessage;

			if ( callMessage.HasOnlySafeType() )
			{
				if ( callMessage.IsSetProperty() )
				{
					this.SetField( callMessage.GetPropertyName(), Convert.ToString( callMessage.GetInArg( 0 ) ) );
					return new ReturnMessage( null, null, 0, callMessage.LogicalCallContext, callMessage );
				}
				else if ( callMessage.IsGetProperty() )
				{
					MethodInfo method = ( MethodInfo )callMessage.MethodBase;

					string value = this.GetField( callMessage.GetPropertyName() );
					return new ReturnMessage( Convert.ChangeType( value, method.ReturnType ), null, 0, callMessage.LogicalCallContext, callMessage );
				}
				else
				{
					RedisCommandAttribute att = callMessage.MethodBase.GetCustomAttributes( true ).OfType<RedisCommandAttribute>().FirstOrDefault();

					if ( att != null )
					{
						MethodInfo method = ( MethodInfo )callMessage.MethodBase;

						if ( typeof( int ).Equals( method.ReturnType ) )
							return callMessage.Return( this.channel.ExecuteInt32, att.Command, this.GetParameters( callMessage ).ToArray() );
					}
					else if ( callMessage.IsCommitMethod() )
						return callMessage.Return( this.Commit );
					else if ( callMessage.IsReadValuesMethod() )
						return callMessage.ReturnVoid( this.ReadValues );
				}
			}

			return new ReturnMessage( new InvalidOperationException(), callMessage );
		}

		private IEnumerable<string> GetParameters( IMethodCallMessage msg )
		{
			yield return this.HashName;
			for ( int index = 0, count = msg.InArgCount; index < count; index++ )
				yield return Convert.ToString( msg.GetInArg( index ) );
		}

		#region IHashObject Members

		public int Commit()
		{
			int fieldChangeCount = this.changedValues.Count;

			if ( fieldChangeCount > 0 )
			{
				string[] parameters = new string[ this.changedValues.Count * 2 ];

				for ( int index = 0, keyIndex = 0; index < parameters.Length; index += 2, keyIndex++ )
				{
					parameters[ index ] = this.changedValues[ keyIndex ];
					parameters[ index + 1 ] = this.values[ this.changedValues[ keyIndex ] ];
				}

				this.channel.ExecuteSingle( "HMSET", parameters.InsertFirst( this.HashName ) );
				this.changedValues = new List<string>();
			}
			return fieldChangeCount;
		}

		public void ReadValues()
		{
			this.ReadValues( true );
		}

		private void ReadValues( bool reload )
		{
			if ( !this.ValuesLoaded || reload )
			{
				this.changedValues = new List<string>();
				this.values = new Dictionary<string, string>();

				var fields = this.channel.ExecuteOrQuery( "HGETALL", this.HashName ).GetEnumerator();

				while ( fields.MoveNext() )
				{
					string field = fields.Current;
					fields.MoveNext();
					string value = fields.Current;

					this.values.Add( field, value );
				}

				this.ValuesLoaded = true;
			}
		}

		private void SetValue( string fieldName, string fieldValue )
		{
			this.channel.ExecuteSingle( "HMSET", this.HashName, fieldName, fieldValue );
		}

		private void SetValue( IDictionary<string, string> values )
		{

		}

		public string GetValue( string fieldName )
		{
			return this.channel.ExecuteSingle( "HGET", this.HashName, fieldName );
		}

		#endregion
	}
}
